/*
 *   The contents of this file are subject to the Mozilla Public License
 *   Version 1.1 (the "License"); you may not use this file except in
 *   compliance with the License. You may obtain a copy of the License at
 *   http://www.mozilla.org/MPL/
 *
 *   Software distributed under the License is distributed on an "AS IS"
 *   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *   License for the specific language governing rights and limitations
 *   under the License.
 *
 *   The Original Code is Matra - the DTD Parser.
 *
 *   The Initial Developer of the Original Code is Conrad S Roche.
 *   Portions created by Conrad S Roche are Copyright (C) Conrad
 *   S Roche. All Rights Reserved.
 *
 *   Alternatively, the contents of this file may be used under the terms
 *   of the GNU GENERAL PUBLIC LICENSE Version 2 or any later version
 *   (the  "[GPL] License"), in which case the
 *   provisions of GPL License are applicable instead of those
 *   above.  If you wish to allow use of your version of this file only
 *   under the terms of the GPL License and not to allow others to use
 *   your version of this file under the MPL, indicate your decision by
 *   deleting  the provisions above and replace  them with the notice and
 *   other provisions required by the GPL License.  If you do not delete
 *   the provisions above, a recipient may use your version of this file
 *   under either the MPL or the GPL License."
 *
 *   [NOTE: The text of this Exhibit A may differ slightly from the text of
 *   the notices in the Source Code files of the Original Code. You should
 *   use the text of this Exhibit A rather than the text found in the
 *   Original Code Source Code for Your Modifications.]
 *
 * Created: Conrad S Roche <derupe at users.sourceforge.net>,  25-Jul-2000
 */

package com.conradroche.matra.decl;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import com.conradroche.matra.dtdparser.ParserFlags;
import com.conradroche.matra.exception.*;

/**
 * Class to hold all the declaration in the DTD.
 *
 * @author: Conrad Roche
 */
public class DocType {

	private Hashtable ElementList, AttributeList, EntityList;

	private Vector rootElements;


	//flags for various purposes
	private boolean foundRootElements;

	//CR: FIXME: This class should hold all the details of the DTD, not the DTDParser!

	/* Methods needed
		getEntityList
		getParameterEntityList
		getRootElements()

		addEntity
		addNotation

	*/
/**
 * DocType constructor comment.
 */
public DocType() {
	super();

	reset();
}
/**
 * Adds an attribute list.
 *
 * @param attList The attribute list to add.
 */
public void addAttList(AttList attList) {

	//CR: TODO: Do we need to check for duplicate attributes too?
	String elemName = attList.getEleName();

	if(elemName == null) {
		//CR: TODO: Handle this!
		return;
	}

	if(AttributeList.get(elemName) == null) {
		AttributeList.put(elemName, attList);
	} else {
		//So, this is a second attribute list for this element
		if(ParserFlags.UO_MULTIPLE_ATTR_LIST) {
			//CR: TODO: show warning
		}
		AttList oldAttList = (AttList) AttributeList.get(elemName);

		/*
		 * 3.3 Attribute-List Declarations [XML Rec 1.0]
		 *
		 * When more than one AttlistDecl is provided for a
		 * given element type, the contents of all those
		 * provided are merged.
		 */
		oldAttList.merge(attList);
		AttributeList.put(elemName, oldAttList);
	}

	ElementType elementType = getElementType(elemName);
	if(elementType == null) {
		if(ParserFlags.UO_MISSING_ELEM_DECL) {
			//CR: TODO: Show warning here
		}
	} else {
		/*
		 * 3.3.1 Attribute Types [XML Rec 1.0]
		 *
		 * Validity constraint: No Notation on Empty Element
		 * 		For compatibility, an attribute of type NOTATION
		 * 		must not be declared on an element declared EMPTY.
		 */
		if(elementType.isEmptyContentModel() && attList.getHasNotationAttribute()) {
			//CR: TODO: throw error here
		}
	}
}
/**
 * Adds an element type declaration.
 *
 * @param eleType The element type declaration.
 *
 * @throws DuplicateDefinitionException If an element type with
 * 			the same name already exists.
 */
public void addElementType(ElementType eleType) throws DuplicateDefinitionException {

	if(ElementList.get(eleType.getName()) == null)
		ElementList.put(eleType.getName(), eleType);
	else {
		String mesg = "Duplicate definition encountered for element type " + eleType.getName();
		System.out.println( mesg );
		throw new DuplicateDefinitionException(mesg);
	}

}
/**
 * Adds an entity.
 *
 * @param ent The entity to add.
 */
public void addEntity(Entity ent) {

	if( EntityList.get(ent.getEntityName()) == null ) //the first definition always overrides the remaining.
		EntityList.put(ent.getEntityName(), ent);
}
/**
 * Adds a parameter entity.
 *
 * @param ent The parameter entity to add.
 */
public void addParamEntity(Entity ent) {

	if( EntityList.get(ent.getEntityName()) == null ) //the first definition always overrides the remaining.
		EntityList.put(ent.getEntityName(), ent);
}
/**
 * Determines the list of potential root
 * elements.
 */
private void findRootElements() {
	//CR: FIXME: SAME AS findRoot() - merge the two, or conditionally call
	//findRoot from here, in case the root is not known.

	if(foundRootElements)
		return;

	foundRootElements = true;

	Enumeration eleList;
	Hashtable candidates = (Hashtable) ElementList.clone(); //list of possible root candidates
	Hashtable remove = new Hashtable(); //elimination list - Elements that cannot be the roots

	eleList = candidates.elements();
	while( eleList.hasMoreElements() ) {
		ElementType ele = (ElementType) eleList.nextElement();
		if( ele.hasChildren() ) {
			String[] children;

			children = ele.getChildrenNames();
			if(children == null)
				continue;

			for(int i = 0; i < children.length; i++) {
				if(!children[i].equals(ele.getName())) //don't remove oneself, even if its its own child.
					remove.put(children[i], "remove");
			}
		}
	}

	//now remove all the candidates who are in the elimination list (remove)
	eleList = remove.keys();
	while( eleList.hasMoreElements() ) {
		String key = (String) eleList.nextElement();
		candidates.remove(key);
	}

	//System.out.println("Candidates = " + candidates);
	if(candidates == null || candidates.size() == 0 ) {
		rootElements = null;
		return;
	}

	//Create a Vector for the root candidates
	rootElements = new Vector();
	String currCandidate;
	Enumeration rootCandidateEnum = candidates.elements();

	currCandidate = ((ElementType) rootCandidateEnum.nextElement()).getName();
	rootElements.addElement(currCandidate);

	while( rootCandidateEnum.hasMoreElements() ) {
		currCandidate = ((ElementType) rootCandidateEnum.nextElement()).getName();
		rootElements.addElement( currCandidate );
	}

}
/**
 * Returns a Hashtable of all attribute lists.
 *
 * @return A Hashtable of all attribute lists.
 */
public Hashtable getAllAttributes() {
	return AttributeList;
}
/**
 * Returns all the attributes for the specified
 * Element Type.
 *
 * @param elementName  The name of the Element Type.
 *
 * @return java.util.Enumeration
 */
public AttList getAttributeList(String elementName) {
	return (AttList) AttributeList.get(elementName);
}
/**
 * Returns a Hashtable of all Element types.
 *
 * @return A Hashtable of all Element types.
 */
public Hashtable getElementList() {

	return ElementList;
}
/**
 * Returns the element names for
 * all the element types.
 *
 * @return An enumeration of element names.
 */
public Enumeration getElementNames() {
	return ElementList.keys();
}

/**
 * Returns the Element Type declaration for
 * the specified element name.
 *
 * @return The Element Type declaration.
 *
 * @param elementName The name of the Element Type.
 */
public ElementType getElementType(String elementName) {
	return (ElementType) ElementList.get(elementName);
}
/**
 * Returns an entity with the specified name.
 *
 * @return An entity with the specified name.
 *
 * @param entityName The name of the entity.
 */
public Entity getEntity(String entityName) {

	return (Entity) EntityList.get(entityName);
}
/**
 * Returns all the entity definitions.
 *
 * @return All the entities.
 */
public Hashtable getEntityList() {
	return EntityList;
}

/**
 * Returns the literal value for the specified
 * entity.
 *
 * @return The literal value.
 *
 * @param entityName The name of the entity.
 */
public String getEntityValue(String entityName) {

	return (String) ((Entity) EntityList.get(entityName)).getLiteralValue();
}
/**
 * Returns all the notations.
 *
 * @return All the notations.
 */
public Enumeration getNotationList() {
	throw new Error("Not Implemented.");
}
/**
 * Returns the parameter entity with
 * the specified name.
 *
 * @param pEntityName The name of the
 * 			parameter entity.
 *
 * @return The parameter entity.
 */
public Entity getParamEntity(String pEntityName) {

	return (Entity) EntityList.get(pEntityName);
}
/**
 * Returns the literal value for the specified
 * entity.
 *
 * @return The literal value.
 *
 * @param pEntityName The name of the
 * 			parameter entity.
 */
public String getParamEntityValue(String pEntityName) {

	return (String) ((Entity) EntityList.get(pEntityName)).getLiteralValue();
}
/**
 * Returns the potential root elements for this doctype.
 *
 * @return an Enumeration object representing the root elements value
 */
public Enumeration getRootElements() {

	if(!foundRootElements)
		findRootElements();

	if(rootElements != null && rootElements.size() != 0) {
		return rootElements.elements();
	} else {
		return null;
	}
}
/**
 * Resets this DocType object.
 */
private void reset() {

	ElementList = new Hashtable();
	AttributeList = new Hashtable();
	EntityList = new Hashtable();

	rootElements = new Vector();

	foundRootElements = false;

}
/**
 * Returns the String representation of
 * this DTD.
 *
 * @return the String representation of
 * this DTD.
 *
 * @deprecated
 */
public String toString() {

	String dtdString = "";

	Enumeration elements = ElementList.elements();
	ElementType elem;
	AttList attlist;

	while(elements.hasMoreElements()) {
		elem = (ElementType) elements.nextElement();
		dtdString += elem.toString() + "\n";

		//get the attribute list for this element
		attlist = (AttList) AttributeList.get(elem.getName());
		if(attlist != null)
			dtdString += attlist.toString() + "\n\n";
		else
			dtdString += "\n";
	}

	//dtdString += "\n";

	//Enumeration attlists = AttributeList.elements();

	//while(attlists.hasMoreElements()) {
		//attlist = (AttList) attlists.nextElement();
		//dtdString += attlist.toString() + "\n\n";
	//}
	return dtdString;
}
}